package beautiful.butterfly.drds.datasource.connection_contexts.spi;

//import beautiful.butterfly.drds.datasource.author.Author;
//import beautiful.butterfly.drds.datasource.author.Authors;

import beautiful.butterfly.drds.datasource.connection_contexts.api.ISql;
import com.mysql.jdbc.ConnectionImpl;
import com.mysql.jdbc.MysqlIO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.net.SocketTimeoutException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

//@Author(name = Authors.LI_YANG)
public class Sql implements ISql {

    public static final Logger logger = LoggerFactory.getLogger(Sql.class);
    protected ConnectionContext connectionContext;
    private final Statement statement;
    protected boolean closed = false;

    public Sql(ConnectionContext connectionContext, Statement statement) {
        super();
        this.connectionContext = connectionContext;
        this.statement = statement;
    }

    public final ConnectionContext getConnectionContext() {
        checkOpen();
        return this.connectionContext;
    }

    public Statement getStatement() {
        return this.statement;
    }

    //
    public void close() throws SQLException {
        if (!this.closed) {
            closeResultSet();
            if (this.statement != null && !this.statement.isClosed()) {
                this.statement.close();
            }
            this.closed = true;
            if (this.connectionContext.getSharedConnection() != null) {
                this.connectionContext.getSharedConnection().removeSql(this);
            }
        }
    }

    public final boolean isClosed() throws SQLException {
        return this.closed;
    }

    protected void checkOpen() throws RuntimeException {
        if (this.closed) {
            Exception exception = null;
            if (this.connectionContext != null) {
                exception = this.connectionContext.getException();
            }
            if (exception != null) {
                throw new RuntimeException("preparedStatement is closed", exception);
            } else {
                throw new RuntimeException("preparedStatement is closed");
            }
        }
    }

    //

    //
    public final void addBatch(String sql) throws SQLException {
        checkOpen();

        try {
            this.statement.addBatch(sql);
        } catch (Exception t) {
            throw handleException(t);
        }
    }

    public int[] executeBatch() throws SQLException {
        checkOpen();

        this.connectionContext.beforeExecute();
        try {
            return this.statement.executeBatch();
        } catch (Exception t) {
            throw handleException(t);
        } finally {
            this.connectionContext.afterExecute();
        }
    }

    public final void clearBatch() throws SQLException {
        if (this.closed) {
            return;
        }

        try {
            this.statement.clearBatch();
        } catch (Exception t) {
            throw handleException(t);
        }
    }

    //
    public final int executeUpdate(String sql) throws SQLException {
        checkOpen();

        this.connectionContext.beforeExecute();
        try {
            return this.statement.executeUpdate(sql);
        } catch (Exception t) {
            throw handleException(t);
        } finally {
            this.connectionContext.afterExecute();
        }
    }

    public final int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
        checkOpen();
        this.connectionContext.beforeExecute();
        try {
            return this.statement.executeUpdate(sql, autoGeneratedKeys);
        } catch (Exception t) {
            throw handleException(t);
        } finally {
            this.connectionContext.afterExecute();
        }
    }

    public final int getUpdateCount() throws SQLException {
        checkOpen();
        try {
            return this.statement.getUpdateCount();
        } catch (Exception t) {
            throw handleException(t);
        }
    }

    public final RowDataMapList getGeneratedKeys() throws SQLException {
        checkOpen();
        try {
            ResultSet resultSet = this.statement.getGeneratedKeys();

            RowDataMapList rowDataMapList = new RowDataMapList(this, resultSet);

            bind(rowDataMapList);

            return rowDataMapList;
        } catch (Exception t) {
            throw handleException(t);
        }
    }

    //
    public final RowDataMapList executeQuery(String sql) throws SQLException {
        checkOpen();
        this.connectionContext.beforeExecute();
        try {
            ResultSet resultSet = this.statement.executeQuery(sql);
            RowDataMapList rowDataMapList = new RowDataMapList(this, resultSet);
            bind(rowDataMapList);
            return rowDataMapList;
        } catch (Exception t) {
            throw handleException(t);
        } finally {
            this.connectionContext.afterExecute();
        }
    }

    //
    protected RowDataMapList rowDataMapList;

    protected void bind(RowDataMapList rowDataMapList) {
        this.rowDataMapList = rowDataMapList;
    }

    protected void closeResultSet() {
        if (this.rowDataMapList == null) {
            return;
        }
        try {
            if (!this.rowDataMapList.isClosed()) {
                this.rowDataMapList.close();
            }
        } catch (SQLException ex) {
            ex.printStackTrace();
            logger.error("closeResultSet error", ex);
        }
    }

    //
    public void setQueryTimeout(int queryTimeout) throws SQLException {
        checkOpen();

        try {
            this.statement.setQueryTimeout(queryTimeout);
        } catch (Exception t) {
            throw handleException(t);
        }
    }

    public final int getQueryTimeout() throws SQLException {
        checkOpen();

        try {
            return this.statement.getQueryTimeout();
        } catch (Exception t) {
            throw handleException(t);
        }
    }

    public void setFetchDirection(int fetchDirection) throws SQLException {
        checkOpen();
        try {
            this.statement.setFetchDirection(fetchDirection);
        } catch (Exception t) {
            throw handleException(t);
        }
    }

    public final int getFetchDirection() throws SQLException {
        checkOpen();

        try {
            return this.statement.getFetchDirection();
        } catch (Exception t) {
            throw handleException(t);
        }
    }

    //
    public void setFetchSize(int fetchSize) throws SQLException {
        checkOpen();

        try {
            this.statement.setFetchSize(fetchSize);
        } catch (Exception t) {
            throw handleException(t);
        }
    }

    public final int getFetchSize() throws SQLException {
        checkOpen();

        try {
            return this.statement.getFetchSize();
        } catch (Exception t) {
            throw handleException(t);
        }
    }

    //
    public void setMaxRows(int maxRows) throws SQLException {
        checkOpen();

        try {
            this.statement.setMaxRows(maxRows);
        } catch (Exception t) {
            throw handleException(t);
        }
    }

    public final int getMaxRows() throws SQLException {
        checkOpen();

        try {
            return this.statement.getMaxRows();
        } catch (Exception t) {
            throw handleException(t);
        }
    }

    //
    public void setMaxFieldSize(int maxFieldSize) throws SQLException {
        checkOpen();
        try {
            this.statement.setMaxFieldSize(maxFieldSize);
        } catch (Exception t) {
            throw handleException(t);
        }
    }

    public int getMaxFieldSize() throws SQLException {
        checkOpen();

        try {
            return this.statement.getMaxFieldSize();
        } catch (Exception t) {
            throw handleException(t);
        }
    }

    public final void cancel() throws SQLException {
        checkOpen();

        try {
            this.statement.cancel();
        } catch (Exception t) {
            throw handleException(t);
        }
    }

    //
    protected SQLException handleException(Exception exception) throws SQLException {
        try {
            handleScoketTimeout(exception);
        } catch (NoSuchFieldException e) {
            logger.error(e.getMessage());
            //
        } catch (IllegalAccessException e) {
            //
            logger.error(e.getMessage());
        } catch (SQLException e) {
            logger.error(e.getMessage());
        }
        return connectionContext.handleException(exception);
    }

    private void handleScoketTimeout(Exception exception) throws SQLException, NoSuchFieldException, IllegalAccessException {
        if (this.connectionContext == null || this.connectionContext.sharedConnection == null) {
            return;
        }
        ConnectionContexts connectionContexts = (ConnectionContexts) this.connectionContext.sharedConnection.abstractConnectionContexts;
        if (connectionContexts == null) {
            return;
        }
        if (!connectionContexts.killWhenSocketReadTimeout) {
            return;
        }
        SQLException sqlException = null;
        if (exception instanceof SQLException) {
            sqlException = (SQLException) exception;
        }
        if (sqlException == null) {
            return;
        }

        Throwable cause = exception.getCause();
        boolean socketReadTimeout = cause instanceof SocketTimeoutException && "Read timed out".equals(cause.getMessage());
        if (!socketReadTimeout) {
            return;
        }
        ConnectionImpl connectionImpl = (ConnectionImpl) this.connectionContext.connection;
        MysqlIO io = connectionImpl.getIO();
        Field field = io.getClass().getField("threadId");
        field.setAccessible(true);
        long threadId = 0;//(Long) field.get(io);
        String killQuery = "KILL QUERY " + threadId;
        //
        ConnectionContext connectionContext = null;
        Statement statement = null;
        try {
            connectionContext = connectionContexts.getConnectionContextWith(1000);
            if (connectionContext == null) {
                return;
            }
            statement = connectionContext.getConnection().createStatement();
            statement.execute(killQuery);
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            JdbcUtils.close(statement);
            connectionContext.close();
        }

    }

}
